/**
 * 
 */
package com.yullage.ae.core;

import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import au.com.bytecode.opencsv.CSVReader;

import com.yullage.ae.attritioner.LinearAttritioner;

/**
 * @author Yu-chun Huang
 * 
 */
public class Mind implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * Logger
	 */
	private static final Logger logger = LoggerFactory.getLogger(Mind.class);

	/**
	 * Zone pool
	 */
	private Map<String, Zone> zonePool = new TreeMap<String, Zone>();

	/**
	 * Path pool
	 */
	private Map<String, Path> pathPool = new TreeMap<String, Path>();

	/**
	 * Energy attritioner
	 */
	private Attritioner attritioner = new LinearAttritioner(0.9, 0.2);

	/**
	 * Current Think Time
	 */
	private long curThinkTime;

	/**
	 * Constructor
	 * 
	 * @param attritioner
	 */
	public Mind(Attritioner attritioner) {
		if (attritioner == null) {
			throw new IllegalArgumentException();
		}

		this.attritioner = attritioner;
	}

	/**
	 * Load mind file from specified file name.
	 * 
	 * @param fileName
	 * @throws IOException
	 */
	public void load(String fileName) throws IOException {
		logger.info("Load mind file: {}.", fileName);

		CSVReader reader = new CSVReader(new FileReader(fileName));
		String[] nextLine;
		while ((nextLine = reader.readNext()) != null) {
			if ((nextLine.length != 3) || "".equals(nextLine[0].trim()) || "".equals(nextLine[2].trim())) {
				logger.error("Corrupted mind file: {}", fileName);
				throw new IllegalArgumentException();
			}

			String zoneName1 = nextLine[0].trim().toUpperCase(); // From zone
			String zoneName2 = nextLine[2].trim().toUpperCase(); // To zone

			String pathName = zoneName1 + "," + zoneName2;
			int numNeurons = Integer.parseInt(nextLine[1].trim());

			// Create new zone if doesn't exist.
			if (!zonePool.containsKey(zoneName1)) {
				zonePool.put(zoneName1, new Zone(zoneName1));
			}

			if (!zonePool.containsKey(zoneName2)) {
				zonePool.put(zoneName2, new Zone(zoneName2));
			}

			// Create new path if doesn't exist.
			if (!pathPool.containsKey(pathName)) {
				Path path = new Path(zonePool.get(zoneName1), zonePool.get(zoneName2), attritioner);
				path.setNumNeurons(numNeurons);

				pathPool.put(pathName, path);

				// Add path references to zones.
				zonePool.get(zoneName1).getOutgoingPaths().add(path);
				zonePool.get(zoneName2).getIncomingPaths().add(path);

				logger.debug("Load zone and path: {} -> ({}) -> {}", new Object[] { zoneName1, numNeurons, zoneName2 });
			} else {
				logger.warn("Duplicated path: {}", pathName);
			}
		}

		reset();
	}

	public void reset() {
		for (Zone zone : zonePool.values()) {
			zone.init();
		}

		for (Path path : pathPool.values()) {
			path.init();
		}

		curThinkTime = 0;
	}

	public void trigger(String zoneName, double intensity) {
		zoneName = zoneName.toUpperCase();

		if (!zonePool.containsKey(zoneName) || intensity < 0) {
			throw new IllegalArgumentException();
		}

		zonePool.get(zoneName).trigger(intensity);
	}

	public void think(long steps) {
		for (long i = 0; i < steps; i++) {
			for (Zone zone : zonePool.values()) {
				zone.fire();
			}

			for (Path path : pathPool.values()) {
				path.discharge();
			}

			curThinkTime++;
		}
	}

	public Attritioner getAttritioner() {
		return attritioner;
	}

	public void setAttritioner(Attritioner attritioner) {
		if (attritioner == null) {
			throw new IllegalArgumentException();
		}

		this.attritioner = attritioner;
		for (Path path : pathPool.values()) {
			path.setAttritioner(attritioner);
		}
	}

	public Map<String, Zone> getZonePool() {
		return zonePool;
	}

	public Map<String, Path> getPathPool() {
		return pathPool;
	}

	public long getCurThinkTime() {
		return curThinkTime;
	}
}
